Required R packages

The diprate package is available from GitHub here: dipDRC

library(diprate)
Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
Registered S3 methods overwritten by 'car':
  method                          from
  influence.merMod                lme4
  cooks.distance.influence.merMod lme4
  dfbeta.influence.merMod         lme4
  dfbetas.influence.merMod        lme4
options(stringsAsFactors=FALSE)

Static data

static <- read.csv("../data/from_CW_via_Slack_20210507/StaticDF.csv", row.names=1)
static <- static[order(static$Culture_Type,static$Cell_Line),]
static[static$Cell_Conc==0,"Cell_Conc"] <- 1
cell_lines <- unique(static$Cell_Line)

Outlier value in CORL279

static <- static[!(static$Cell_Line=="CORL279" & static$RLU < 10),]

Code for reproducing figures

Correlation between luminescence and cell count

par(mfrow=c(2,5))
linear_models_linscale <- lapply(cell_lines, function(cl) {
        dat <- static[static$Cell_Line==cl,]
        culture_type <- unique(dat$Culture_Type)
        m <- lm(RLU ~ Cell_Conc, data = dat)
        yr <- c(0,max(dat$RLU))
        plot(RLU ~ Cell_Conc, data=dat, main=paste0(cl," (",culture_type,")")
             #, xlim=c(0,14), ylim=c(7,18)
             )
        abline(m, col="blue")
        text(0, yr[2], pos=4, paste("slope =",signif(f2[cl,2],3)))
        text(0, yr[2]-(yr[2]*.05), pos=4, expression(R^2))
        text(200, yr[2]-(yr[2]*.05), pos=4, paste("=", signif(r2[cl],3)))
        return(m)
    })

names(linear_models_linscale) <- cell_lines
par(mfrow=c(2,5))
linear_models <- lapply(cell_lines, function(cl) {
        dat <- static[static$Cell_Line==cl,]
        culture_type <- unique(dat$Culture_Type)
        m <- lm(log2(RLU) ~ log2(Cell_Conc), data = dat)
        plot(log2(RLU) ~ log2(Cell_Conc), data=dat, main=paste0(cl," (",culture_type,")"), xlim=c(0,14), ylim=c(7,18))
        abline(m, col="blue")
        return(m)
    })
names(linear_models) <- cell_lines

Need 2-part function to accommodate minimum values

Assuming first part of data is at some minimum value and not associated with any actual cell count (lower limit of detection), which would result in slope=0 (values are constant until some minimum number of cells is achieved).

lagLine <- function (x, lower, slope, br = 64) sapply(x, function(z) ifelse(z <= br, lower, lower + (z-br) * slope))

fitLagLin <- function(x, y, start_list=list(lower=5, slope=1, br=1)) 
    nls(y ~ lagLine(x=x, lower, slope, br),
        start = start_list,
        algorithm="port",
        control=nls.control(maxiter=500)
        )

Fit the lag-linear model to data

par(mfrow=c(2,5))
lag_linear_models <- lapply(cell_lines, function(cl) {
    m <- tryCatch({
        dat <- static[static$Cell_Line==cl,]
        culture_type <- unique(dat$Culture_Type)
        m <- fitLagLin(log2(dat$Cell_Conc), log2(dat$RLU))
    }, error=function(e) { return(e) })
    # if(culture_type == "Adherent") 
    # {
    #     xr <- c(4,12)
    # } else {
    #     xr <- c(6,14)
    # }
    xr <- c(0,14)
    plot(log2(RLU) ~ log2(Cell_Conc), data=dat, main=paste0(cl," (",culture_type,")"), xlim=xr, ylim=c(7,18))
    if(class(m)[1] != "nls")
    {
        m <- lm(log2(RLU) ~ log2(Cell_Conc), data=dat[dat$Cell_Conc>1,])
        abline(m, col="blue", lwd=2)
    } else {
        curve(from=0.5,to=18, lagLine(x, lower=coef(m)['lower'], 
                                      slope=coef(m)['slope'], 
                                      br=coef(m)['br']), 
              col="blue", lwd=2, add=TRUE)
    }
    # text(12, 9, paste("Adj R2 ="))
    return(m)
})

names(lag_linear_models) <- cell_lines

Try eliminating controls

Assume all luminescence values with cells produce detectable signal.

par(mfrow=c(2,5))
linear_models <- lapply(cell_lines, function(cl) {
        dat <- static[static$Cell_Line==cl & static$Cell_Conc >1,]
        culture_type <- unique(dat$Culture_Type)
        m <- lm(log2(RLU) ~ log2(Cell_Conc), data = dat)
        plot(log2(RLU) ~ log2(Cell_Conc), data=dat, main=paste0(cl," (",culture_type,")"), xlim=c(0,14), ylim=c(7,18))
        abline(m, col="blue")
        return(m)
    })

names(linear_models) <- cell_lines

Compare to linear models

Assuming the lowest number of cells is above the threshold of detection, will remove the no cells control and fit remaining data.

dat <- static[static$Cell_Conc > 1,]
m2 <- lme4::lmList(log2(RLU) ~ log2(Cell_Conc) | Cell_Line, data=dat)
f2 <- coef(m2)
r2 <- unlist(summary(m2)$adj.r.squared)
par(mfrow=c(2,5))
temp <- lapply(cell_lines, function(cl) {
    dtp <- dat[dat$Cell_Line==cl,]
    culture_type <- unique(dtp[dtp$Cell_Line==cl,'Culture_Type'])
    if(culture_type == "Adherent") 
    {
        xr <- c(5,12)
    } else {
        xr <- c(7,14)
    }
    plot(log2(RLU) ~ log2(Cell_Conc), 
         data=dtp, 
         main=paste0(cl," (",culture_type,")"), 
         xlim=xr, ylim=c(7,18))
    abline(m2[[cl]], col="blue", lwd=2)
    text(xr[1]+0.25, 17, pos=4, paste("slope =",signif(f2[cl,2],3)))
    text(xr[1]+0.25, 16, pos=4, expression(R^2))
    text(xr[1]+1, 16, pos=4, paste("=", signif(r2[cl],3)))
})

fitLagLin1 <- function(x, y, start_list=list(lower=5, br=1)) 
    nls(y ~ lagLine(x=x, lower, slope=1, br),
        start = start_list,
        algorithm="port",
        control=nls.control(maxiter=500)
        )

par(mfrow=c(2,5))
lag_linear1_models <- lapply(cell_lines, function(cl) {
    m <- tryCatch({
        dat <- static[static$Cell_Line==cl,]
        culture_type <- unique(dat$Culture_Type)
        m <- fitLagLin1(log2(dat$Cell_Conc), log2(dat$RLU))
    }, error=function(e) { return(e) })
    # if(culture_type == "Adherent") 
    # {
    #     xr <- c(4,12)
    # } else {
    #     xr <- c(6,14)
    # }
    xr <- c(0,14)
    plot(log2(RLU) ~ log2(Cell_Conc), data=dat, main=paste0(cl," (",culture_type,")"), xlim=xr, ylim=c(7,18))
    if(class(m)[1] != "nls")
    {
        m <- lm(log2(RLU) ~ log2(Cell_Conc), data=dat[dat$Cell_Conc>1,])
        abline(m, col="blue", lwd=2)
    } else {
        curve(from=0.5,to=18, lagLine(x, lower=coef(m)['lower'], 
                                      slope=1, 
                                      br=coef(m)['br']), 
              col="blue", lwd=2, add=TRUE)
    }
    # text(12, 9, paste("Adj R2 ="))
    return(m)
})

names(lag_linear1_models) <- cell_lines

Combined cell count & lum data

Cells seeded on Dec 15 Drug added:2 pm Dec 16; 60 + 20 µl of drug Data acquisition began:

lcc <- read.csv('../data/from_CW_via_Slack_20210507/20201216_Lum_CellCounts_Thunor.csv', row.names=1, as.is=TRUE)
lcc <- lcc[,-1]
lcc$uid <- paste(lcc$upid,lcc$well,sep="_")
lcc <- lcc[order(lcc$uid,lcc$time),]
lcc <- lcc[lcc$time <= 96,]
lcc_cell_lines <- unique(lcc$cell.line)
ctrls <- lapply(lcc_cell_lines, function(cl) lcc[lcc$cell.line==cl & lcc$drug1.conc==0,])
names(ctrls) <- lcc_cell_lines

Cell counts

par(mfrow=c(2,2))
invisible(lapply(names(ctrls), function(n) do.call(plotGC, 
        append(getGCargs(ctrls[[n]], dat.col=c("time","Cell_Count","uid")),list(main=n, leg=FALSE)))))

Luminscence

par(mfrow=c(2,2))
invisible(lapply(names(ctrls), function(n) do.call(plotGC, 
        append(getGCargs(ctrls[[n]], dat.col=c("time","RLU","uid")),list(main=n, leg=FALSE)))))

Sum of all control cell counts at each time point

sumc <- do.call(rbind, lapply(lcc_cell_lines, function(cl) 
    {
        counts <- sapply(unique(ctrls[[cl]]$time), function(i) 
            sum(ctrls[[cl]][ctrls[[cl]]$time==i,"Cell_Count"]))
        times <- unique(ctrls[[cl]]$time)
        data.frame(cell.line=cl, time=times, cell.count=counts)
}))
sumc$popdubs <- log2norm(sumc$cell.count,sumc$cell.line)
sumc$cell.line <- as.character(sumc$cell.line)
# h1048_sumc <- data.frame(time=unique(h1048$time), cell.count=h1048_sumc)
# plot(log2(cell.count)-log2(cell.count)[1] ~ time, data=h1048_sumc, type="l", ylab="Population doublings")
par(mfrow=c(3,2))

invisible(lapply(lcc_cell_lines[lcc_cell_lines!="WM88"], function(cl)
{
    dtp <- ctrls[[cl]]
    invisible(do.call(plotGC, append(getGCargs(dtp, dat.col=c("time","Cell_Count","uid")),
                                     list(main=paste0(cl,", cell count"), leg=FALSE))))
    invisible(do.call(plotGC, append(getGCargs(dtp, dat.col=c("time","RLU","uid")),
                                     list(main=paste0(cl,", lum"), leg=FALSE, ylim=c(-1,3)))))
    lines(sumc[sumc$cell.line==cl,"time"], sumc[sumc$cell.line==cl,"popdubs"], lwd=3)
}))

lumo <- read.csv("../data/from_CW_via_Slack_20210507/050819_RTglowDF.csv")
lumo <- lumo[,-1]

lumo_cell_lines <- unique(lumo$cell.line)
lumo_ctrl_dat <- lapply(lumo_cell_lines, function(cl) lumo[lumo$cell.line==cl & lumo$drug1.conc==0 & lumo$drug1!="DMSO",])
names(lumo_ctrl_dat) <- lumo_cell_lines
par(mfrow=c(2,4))

invisible(lapply(lumo_cell_lines, function(cl)
{
    dtp <- lumo_ctrl_dat[[cl]]
    # plot(dtp$TotHour, dtp$RLU)
    
    invisible(do.call(plotGC, append(getGCargs(dtp, dat.col=c("TotHour","RLU","well"), arg.name = c("time", "cell.count", "ids")),
                                     list(main=cl, leg=FALSE))))
}))

do.call(plotGC, append(getGCargs(a, dat.col=c("TotHour","RLU","well"), arg.name = c("time", "cell.count", "ids")),
                                     list(main="CORL279", leg=FALSE)))
NULL

LS0tCnRpdGxlOiAiUmVhbC10aW1lIGx1bWluZXNjZW5jZSBlbmFibGVzIGVzdGltYXRpb24gb2YgZHJ1Zy1pbmR1Y2VkIHByb2xpZmVyYXRpb24gcmF0ZXMgaW4gYWRoZXJlbnQgYW5kIHN1c3BlbnNpb24gY2VsbCBsaW5lcyIKYXV0aG9yOiAiRGFycmVuIFR5c29uICYgQ2xheXRvbiBXYW5kaXNoaW4iCmRhdGU6ICIwNS8wOS8yMDIxIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojIyBSZXF1aXJlZCBSIHBhY2thZ2VzClRoZSBgZGlwcmF0ZWAgcGFja2FnZSBpcyBhdmFpbGFibGUgZnJvbSBHaXRIdWIgaGVyZTogW2RpcERSQ10oaHR0cHM6Ly93d3cuZ2l0aHViLmNvbS9RdWxhYi1WVS9kaXBEUkMpCmBgYHtyIFNldHVwfQpsaWJyYXJ5KGRpcHJhdGUpCm9wdGlvbnMoc3RyaW5nc0FzRmFjdG9ycz1GQUxTRSkKYGBgCgojIyBTdGF0aWMgZGF0YQpgYGB7ciBMb2FkIGRhdGF9CnN0YXRpYyA8LSByZWFkLmNzdigiLi4vZGF0YS9mcm9tX0NXX3ZpYV9TbGFja18yMDIxMDUwNy9TdGF0aWNERi5jc3YiLCByb3cubmFtZXM9MSkKc3RhdGljIDwtIHN0YXRpY1tvcmRlcihzdGF0aWMkQ3VsdHVyZV9UeXBlLHN0YXRpYyRDZWxsX0xpbmUpLF0Kc3RhdGljW3N0YXRpYyRDZWxsX0NvbmM9PTAsIkNlbGxfQ29uYyJdIDwtIDEKY2VsbF9saW5lcyA8LSB1bmlxdWUoc3RhdGljJENlbGxfTGluZSkKYGBgCiMjIyMgT3V0bGllciB2YWx1ZSBpbiBDT1JMMjc5CmBgYHtyfQpzdGF0aWMgPC0gc3RhdGljWyEoc3RhdGljJENlbGxfTGluZT09IkNPUkwyNzkiICYgc3RhdGljJFJMVSA8IDEwKSxdCmBgYAoKCiMjIENvZGUgZm9yIHJlcHJvZHVjaW5nIGZpZ3VyZXMKIyMjIENvcnJlbGF0aW9uIGJldHdlZW4gbHVtaW5lc2NlbmNlIGFuZCBjZWxsIGNvdW50CmBgYHtyIENlbGwgY291bnQgJiBsdW1pbmVzY2VuY2UgbGluZWFyIHNjYWxlLCBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD03fQpwYXIobWZyb3c9YygyLDUpKQpsaW5lYXJfbW9kZWxzX2xpbnNjYWxlIDwtIGxhcHBseShjZWxsX2xpbmVzLCBmdW5jdGlvbihjbCkgewogICAgICAgIGRhdCA8LSBzdGF0aWNbc3RhdGljJENlbGxfTGluZT09Y2wsXQogICAgICAgIGN1bHR1cmVfdHlwZSA8LSB1bmlxdWUoZGF0JEN1bHR1cmVfVHlwZSkKICAgICAgICBtIDwtIGxtKFJMVSB+IENlbGxfQ29uYywgZGF0YSA9IGRhdCkKICAgICAgICB5ciA8LSBjKDAsbWF4KGRhdCRSTFUpKQogICAgICAgIHBsb3QoUkxVIH4gQ2VsbF9Db25jLCBkYXRhPWRhdCwgbWFpbj1wYXN0ZTAoY2wsIiAoIixjdWx0dXJlX3R5cGUsIikiKQogICAgICAgICAgICAgIywgeGxpbT1jKDAsMTQpLCB5bGltPWMoNywxOCkKICAgICAgICAgICAgICkKICAgICAgICBhYmxpbmUobSwgY29sPSJibHVlIikKICAgICAgICB0ZXh0KDAsIHlyWzJdLCBwb3M9NCwgcGFzdGUoInNsb3BlID0iLHNpZ25pZihmMltjbCwyXSwzKSkpCiAgICAgICAgdGV4dCgwLCB5clsyXS0oeXJbMl0qLjA1KSwgcG9zPTQsIGV4cHJlc3Npb24oUl4yKSkKICAgICAgICB0ZXh0KDIwMCwgeXJbMl0tKHlyWzJdKi4wNSksIHBvcz00LCBwYXN0ZSgiPSIsIHNpZ25pZihyMltjbF0sMykpKQogICAgICAgIHJldHVybihtKQogICAgfSkKbmFtZXMobGluZWFyX21vZGVsc19saW5zY2FsZSkgPC0gY2VsbF9saW5lcwpgYGAKCmBgYHtyIENlbGwgY291bnQgJiBsdW1pbmVzY2VuY2UgbG9nIHNjYWxlLCBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD03fQpwYXIobWZyb3c9YygyLDUpKQpsaW5lYXJfbW9kZWxzIDwtIGxhcHBseShjZWxsX2xpbmVzLCBmdW5jdGlvbihjbCkgewogICAgICAgIGRhdCA8LSBzdGF0aWNbc3RhdGljJENlbGxfTGluZT09Y2wsXQogICAgICAgIGN1bHR1cmVfdHlwZSA8LSB1bmlxdWUoZGF0JEN1bHR1cmVfVHlwZSkKICAgICAgICBtIDwtIGxtKGxvZzIoUkxVKSB+IGxvZzIoQ2VsbF9Db25jKSwgZGF0YSA9IGRhdCkKICAgICAgICBwbG90KGxvZzIoUkxVKSB+IGxvZzIoQ2VsbF9Db25jKSwgZGF0YT1kYXQsIG1haW49cGFzdGUwKGNsLCIgKCIsY3VsdHVyZV90eXBlLCIpIiksIHhsaW09YygwLDE0KSwgeWxpbT1jKDcsMTgpKQogICAgICAgIGFibGluZShtLCBjb2w9ImJsdWUiKQogICAgICAgIHJldHVybihtKQogICAgfSkKbmFtZXMobGluZWFyX21vZGVscykgPC0gY2VsbF9saW5lcwpgYGAKCiMjIyMgTmVlZCAyLXBhcnQgZnVuY3Rpb24gdG8gYWNjb21tb2RhdGUgbWluaW11bSB2YWx1ZXMKQXNzdW1pbmcgZmlyc3QgcGFydCBvZiBkYXRhIGlzIGF0IHNvbWUgbWluaW11bSB2YWx1ZSBhbmQgbm90IGFzc29jaWF0ZWQgd2l0aCBhbnkgYWN0dWFsIGNlbGwgY291bnQgKGxvd2VyIGxpbWl0IG9mIGRldGVjdGlvbiksIHdoaWNoIHdvdWxkIHJlc3VsdCBpbiBgc2xvcGU9MGAgKHZhbHVlcyBhcmUgY29uc3RhbnQgdW50aWwgc29tZSBtaW5pbXVtIG51bWJlciBvZiBjZWxscyBpcyBhY2hpZXZlZCkuCmBgYHtyIExhZy1saW5lYXIgZnVuY3Rpb259CmxhZ0xpbmUgPC0gZnVuY3Rpb24gKHgsIGxvd2VyLCBzbG9wZSwgYnIgPSA2NCkgc2FwcGx5KHgsIGZ1bmN0aW9uKHopIGlmZWxzZSh6IDw9IGJyLCBsb3dlciwgbG93ZXIgKyAoei1icikgKiBzbG9wZSkpCgpmaXRMYWdMaW4gPC0gZnVuY3Rpb24oeCwgeSwgc3RhcnRfbGlzdD1saXN0KGxvd2VyPTUsIHNsb3BlPTEsIGJyPTEpKSAKICAgIG5scyh5IH4gbGFnTGluZSh4PXgsIGxvd2VyLCBzbG9wZSwgYnIpLAogICAgICAgIHN0YXJ0ID0gc3RhcnRfbGlzdCwKICAgICAgICBhbGdvcml0aG09InBvcnQiLAogICAgICAgIGNvbnRyb2w9bmxzLmNvbnRyb2wobWF4aXRlcj01MDApCiAgICAgICAgKQpgYGAKCiMjIyMgRml0IHRoZSBsYWctbGluZWFyIG1vZGVsIHRvIGRhdGEKYGBge3IgTGFnLWxpbmVhciBtb2RlbCBmaXRzLCBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD03fQpwYXIobWZyb3c9YygyLDUpKQpsYWdfbGluZWFyX21vZGVscyA8LSBsYXBwbHkoY2VsbF9saW5lcywgZnVuY3Rpb24oY2wpIHsKICAgIG0gPC0gdHJ5Q2F0Y2goewogICAgICAgIGRhdCA8LSBzdGF0aWNbc3RhdGljJENlbGxfTGluZT09Y2wsXQogICAgICAgIGN1bHR1cmVfdHlwZSA8LSB1bmlxdWUoZGF0JEN1bHR1cmVfVHlwZSkKICAgICAgICBtIDwtIGZpdExhZ0xpbihsb2cyKGRhdCRDZWxsX0NvbmMpLCBsb2cyKGRhdCRSTFUpKQogICAgfSwgZXJyb3I9ZnVuY3Rpb24oZSkgeyByZXR1cm4oZSkgfSkKICAgICMgaWYoY3VsdHVyZV90eXBlID09ICJBZGhlcmVudCIpIAogICAgIyB7CiAgICAjICAgICB4ciA8LSBjKDQsMTIpCiAgICAjIH0gZWxzZSB7CiAgICAjICAgICB4ciA8LSBjKDYsMTQpCiAgICAjIH0KICAgIHhyIDwtIGMoMCwxNCkKICAgIHBsb3QobG9nMihSTFUpIH4gbG9nMihDZWxsX0NvbmMpLCBkYXRhPWRhdCwgbWFpbj1wYXN0ZTAoY2wsIiAoIixjdWx0dXJlX3R5cGUsIikiKSwgeGxpbT14ciwgeWxpbT1jKDcsMTgpKQogICAgaWYoY2xhc3MobSlbMV0gIT0gIm5scyIpCiAgICB7CiAgICAgICAgbSA8LSBsbShsb2cyKFJMVSkgfiBsb2cyKENlbGxfQ29uYyksIGRhdGE9ZGF0W2RhdCRDZWxsX0NvbmM+MSxdKQogICAgICAgIGFibGluZShtLCBjb2w9ImJsdWUiLCBsd2Q9MikKICAgIH0gZWxzZSB7CiAgICAgICAgY3VydmUoZnJvbT0wLjUsdG89MTgsIGxhZ0xpbmUoeCwgbG93ZXI9Y29lZihtKVsnbG93ZXInXSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2xvcGU9Y29lZihtKVsnc2xvcGUnXSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnI9Y29lZihtKVsnYnInXSksIAogICAgICAgICAgICAgIGNvbD0iYmx1ZSIsIGx3ZD0yLCBhZGQ9VFJVRSkKICAgIH0KICAgICMgdGV4dCgxMiwgOSwgcGFzdGUoIkFkaiBSMiA9IikpCiAgICByZXR1cm4obSkKfSkKbmFtZXMobGFnX2xpbmVhcl9tb2RlbHMpIDwtIGNlbGxfbGluZXMKYGBgCgoKIyMjIyBUcnkgZWxpbWluYXRpbmcgY29udHJvbHMKQXNzdW1lIGFsbCBsdW1pbmVzY2VuY2UgdmFsdWVzIHdpdGggY2VsbHMgcHJvZHVjZSBkZXRlY3RhYmxlIHNpZ25hbC4KYGBge3IgQ2VsbCBjb3VudCAmIGx1bWluZXNjZW5jZSBtaW51cyBjb250cm9sLCBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD03fQpwYXIobWZyb3c9YygyLDUpKQpsaW5lYXJfbW9kZWxzIDwtIGxhcHBseShjZWxsX2xpbmVzLCBmdW5jdGlvbihjbCkgewogICAgICAgIGRhdCA8LSBzdGF0aWNbc3RhdGljJENlbGxfTGluZT09Y2wgJiBzdGF0aWMkQ2VsbF9Db25jID4xLF0KICAgICAgICBjdWx0dXJlX3R5cGUgPC0gdW5pcXVlKGRhdCRDdWx0dXJlX1R5cGUpCiAgICAgICAgbSA8LSBsbShsb2cyKFJMVSkgfiBsb2cyKENlbGxfQ29uYyksIGRhdGEgPSBkYXQpCiAgICAgICAgcGxvdChsb2cyKFJMVSkgfiBsb2cyKENlbGxfQ29uYyksIGRhdGE9ZGF0LCBtYWluPXBhc3RlMChjbCwiICgiLGN1bHR1cmVfdHlwZSwiKSIpLCB4bGltPWMoMCwxNCksIHlsaW09Yyg3LDE4KSkKICAgICAgICBhYmxpbmUobSwgY29sPSJibHVlIikKICAgICAgICByZXR1cm4obSkKICAgIH0pCm5hbWVzKGxpbmVhcl9tb2RlbHMpIDwtIGNlbGxfbGluZXMKYGBgCgoKCiMjIyMgQ29tcGFyZSB0byBsaW5lYXIgbW9kZWxzCkFzc3VtaW5nIHRoZSBsb3dlc3QgbnVtYmVyIG9mIGNlbGxzIGlzIGFib3ZlIHRoZSB0aHJlc2hvbGQgb2YgZGV0ZWN0aW9uLCB3aWxsIHJlbW92ZSB0aGUgbm8gY2VsbHMgY29udHJvbCBhbmQgZml0IHJlbWFpbmluZyBkYXRhLgpgYGB7cn0KZGF0IDwtIHN0YXRpY1tzdGF0aWMkQ2VsbF9Db25jID4gMSxdCm0yIDwtIGxtZTQ6OmxtTGlzdChsb2cyKFJMVSkgfiBsb2cyKENlbGxfQ29uYykgfCBDZWxsX0xpbmUsIGRhdGE9ZGF0KQpmMiA8LSBjb2VmKG0yKQpyMiA8LSB1bmxpc3Qoc3VtbWFyeShtMikkYWRqLnIuc3F1YXJlZCkKYGBgCgpgYGB7ciBMaW5lYXIgbW9kZWxzLCBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD03fQpwYXIobWZyb3c9YygyLDUpKQp0ZW1wIDwtIGxhcHBseShjZWxsX2xpbmVzLCBmdW5jdGlvbihjbCkgewogICAgZHRwIDwtIGRhdFtkYXQkQ2VsbF9MaW5lPT1jbCxdCiAgICBjdWx0dXJlX3R5cGUgPC0gdW5pcXVlKGR0cFtkdHAkQ2VsbF9MaW5lPT1jbCwnQ3VsdHVyZV9UeXBlJ10pCiAgICBpZihjdWx0dXJlX3R5cGUgPT0gIkFkaGVyZW50IikgCiAgICB7CiAgICAgICAgeHIgPC0gYyg1LDEyKQogICAgfSBlbHNlIHsKICAgICAgICB4ciA8LSBjKDcsMTQpCiAgICB9CiAgICBwbG90KGxvZzIoUkxVKSB+IGxvZzIoQ2VsbF9Db25jKSwgCiAgICAgICAgIGRhdGE9ZHRwLCAKICAgICAgICAgbWFpbj1wYXN0ZTAoY2wsIiAoIixjdWx0dXJlX3R5cGUsIikiKSwgCiAgICAgICAgIHhsaW09eHIsIHlsaW09Yyg3LDE4KSkKICAgIGFibGluZShtMltbY2xdXSwgY29sPSJibHVlIiwgbHdkPTIpCiAgICB0ZXh0KHhyWzFdKzAuMjUsIDE3LCBwb3M9NCwgcGFzdGUoInNsb3BlID0iLHNpZ25pZihmMltjbCwyXSwzKSkpCiAgICB0ZXh0KHhyWzFdKzAuMjUsIDE2LCBwb3M9NCwgZXhwcmVzc2lvbihSXjIpKQogICAgdGV4dCh4clsxXSsxLCAxNiwgcG9zPTQsIHBhc3RlKCI9Iiwgc2lnbmlmKHIyW2NsXSwzKSkpCn0pCgpgYGAKCmBgYHtyIExhZy1saW5lYXIgc2xvcGUgZXEgMSBtb2RlbCBmaXRzLCBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD03fQpmaXRMYWdMaW4xIDwtIGZ1bmN0aW9uKHgsIHksIHN0YXJ0X2xpc3Q9bGlzdChsb3dlcj01LCBicj0xKSkgCiAgICBubHMoeSB+IGxhZ0xpbmUoeD14LCBsb3dlciwgc2xvcGU9MSwgYnIpLAogICAgICAgIHN0YXJ0ID0gc3RhcnRfbGlzdCwKICAgICAgICBhbGdvcml0aG09InBvcnQiLAogICAgICAgIGNvbnRyb2w9bmxzLmNvbnRyb2wobWF4aXRlcj01MDApCiAgICAgICAgKQoKcGFyKG1mcm93PWMoMiw1KSkKbGFnX2xpbmVhcjFfbW9kZWxzIDwtIGxhcHBseShjZWxsX2xpbmVzLCBmdW5jdGlvbihjbCkgewogICAgbSA8LSB0cnlDYXRjaCh7CiAgICAgICAgZGF0IDwtIHN0YXRpY1tzdGF0aWMkQ2VsbF9MaW5lPT1jbCxdCiAgICAgICAgY3VsdHVyZV90eXBlIDwtIHVuaXF1ZShkYXQkQ3VsdHVyZV9UeXBlKQogICAgICAgIG0gPC0gZml0TGFnTGluMShsb2cyKGRhdCRDZWxsX0NvbmMpLCBsb2cyKGRhdCRSTFUpKQogICAgfSwgZXJyb3I9ZnVuY3Rpb24oZSkgeyByZXR1cm4oZSkgfSkKICAgICMgaWYoY3VsdHVyZV90eXBlID09ICJBZGhlcmVudCIpIAogICAgIyB7CiAgICAjICAgICB4ciA8LSBjKDQsMTIpCiAgICAjIH0gZWxzZSB7CiAgICAjICAgICB4ciA8LSBjKDYsMTQpCiAgICAjIH0KICAgIHhyIDwtIGMoMCwxNCkKICAgIHBsb3QobG9nMihSTFUpIH4gbG9nMihDZWxsX0NvbmMpLCBkYXRhPWRhdCwgbWFpbj1wYXN0ZTAoY2wsIiAoIixjdWx0dXJlX3R5cGUsIikiKSwgeGxpbT14ciwgeWxpbT1jKDcsMTgpKQogICAgaWYoY2xhc3MobSlbMV0gIT0gIm5scyIpCiAgICB7CiAgICAgICAgbSA8LSBsbShsb2cyKFJMVSkgfiBsb2cyKENlbGxfQ29uYyksIGRhdGE9ZGF0W2RhdCRDZWxsX0NvbmM+MSxdKQogICAgICAgIGFibGluZShtLCBjb2w9ImJsdWUiLCBsd2Q9MikKICAgIH0gZWxzZSB7CiAgICAgICAgY3VydmUoZnJvbT0wLjUsdG89MTgsIGxhZ0xpbmUoeCwgbG93ZXI9Y29lZihtKVsnbG93ZXInXSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2xvcGU9MSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnI9Y29lZihtKVsnYnInXSksIAogICAgICAgICAgICAgIGNvbD0iYmx1ZSIsIGx3ZD0yLCBhZGQ9VFJVRSkKICAgIH0KICAgICMgdGV4dCgxMiwgOSwgcGFzdGUoIkFkaiBSMiA9IikpCiAgICByZXR1cm4obSkKfSkKbmFtZXMobGFnX2xpbmVhcjFfbW9kZWxzKSA8LSBjZWxsX2xpbmVzCmBgYAoKCgojIyBDb21iaW5lZCBjZWxsIGNvdW50ICYgbHVtIGRhdGEKQ2VsbHMgc2VlZGVkIG9uIERlYyAxNQpEcnVnIGFkZGVkOjIgcG0gRGVjIDE2OyA2MCArIDIwIMK1bCBvZiBkcnVnCkRhdGEgYWNxdWlzaXRpb24gYmVnYW46IAoKYGBge3J9CmxjYyA8LSByZWFkLmNzdignLi4vZGF0YS9mcm9tX0NXX3ZpYV9TbGFja18yMDIxMDUwNy8yMDIwMTIxNl9MdW1fQ2VsbENvdW50c19UaHVub3IuY3N2Jywgcm93Lm5hbWVzPTEsIGFzLmlzPVRSVUUpCmxjYyA8LSBsY2NbLC0xXQpsY2MkdWlkIDwtIHBhc3RlKGxjYyR1cGlkLGxjYyR3ZWxsLHNlcD0iXyIpCmxjYyA8LSBsY2Nbb3JkZXIobGNjJHVpZCxsY2MkdGltZSksXQpsY2MgPC0gbGNjW2xjYyR0aW1lIDw9IDk2LF0KYGBgCgoKYGBge3J9CmxjY19jZWxsX2xpbmVzIDwtIHVuaXF1ZShsY2MkY2VsbC5saW5lKQpjdHJscyA8LSBsYXBwbHkobGNjX2NlbGxfbGluZXMsIGZ1bmN0aW9uKGNsKSBsY2NbbGNjJGNlbGwubGluZT09Y2wgJiBsY2MkZHJ1ZzEuY29uYz09MCxdKQpuYW1lcyhjdHJscykgPC0gbGNjX2NlbGxfbGluZXMKYGBgCgojIyMjIENlbGwgY291bnRzCmBgYHtyIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTZ9CnBhcihtZnJvdz1jKDIsMikpCmludmlzaWJsZShsYXBwbHkobmFtZXMoY3RybHMpLCBmdW5jdGlvbihuKSBkby5jYWxsKHBsb3RHQywgCiAgICAgICAgYXBwZW5kKGdldEdDYXJncyhjdHJsc1tbbl1dLCBkYXQuY29sPWMoInRpbWUiLCJDZWxsX0NvdW50IiwidWlkIikpLGxpc3QobWFpbj1uLCBsZWc9RkFMU0UpKSkpKQpgYGAKIyMjIyBMdW1pbnNjZW5jZQpgYGB7ciBMdW1pbmVzY2VuY2UsIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTZ9CnBhcihtZnJvdz1jKDIsMikpCmludmlzaWJsZShsYXBwbHkobmFtZXMoY3RybHMpLCBmdW5jdGlvbihuKSBkby5jYWxsKHBsb3RHQywgCiAgICAgICAgYXBwZW5kKGdldEdDYXJncyhjdHJsc1tbbl1dLCBkYXQuY29sPWMoInRpbWUiLCJSTFUiLCJ1aWQiKSksbGlzdChtYWluPW4sIGxlZz1GQUxTRSkpKSkpCmBgYAoKIyMjIyBTdW0gb2YgYWxsIGNvbnRyb2wgY2VsbCBjb3VudHMgYXQgZWFjaCB0aW1lIHBvaW50CmBgYHtyfQpzdW1jIDwtIGRvLmNhbGwocmJpbmQsIGxhcHBseShsY2NfY2VsbF9saW5lcywgZnVuY3Rpb24oY2wpIAogICAgewogICAgICAgIGNvdW50cyA8LSBzYXBwbHkodW5pcXVlKGN0cmxzW1tjbF1dJHRpbWUpLCBmdW5jdGlvbihpKSAKICAgICAgICAgICAgc3VtKGN0cmxzW1tjbF1dW2N0cmxzW1tjbF1dJHRpbWU9PWksIkNlbGxfQ291bnQiXSkpCiAgICAgICAgdGltZXMgPC0gdW5pcXVlKGN0cmxzW1tjbF1dJHRpbWUpCiAgICAgICAgZGF0YS5mcmFtZShjZWxsLmxpbmU9Y2wsIHRpbWU9dGltZXMsIGNlbGwuY291bnQ9Y291bnRzKQp9KSkKc3VtYyRwb3BkdWJzIDwtIGxvZzJub3JtKHN1bWMkY2VsbC5jb3VudCxzdW1jJGNlbGwubGluZSkKc3VtYyRjZWxsLmxpbmUgPC0gYXMuY2hhcmFjdGVyKHN1bWMkY2VsbC5saW5lKQojIGgxMDQ4X3N1bWMgPC0gZGF0YS5mcmFtZSh0aW1lPXVuaXF1ZShoMTA0OCR0aW1lKSwgY2VsbC5jb3VudD1oMTA0OF9zdW1jKQojIHBsb3QobG9nMihjZWxsLmNvdW50KS1sb2cyKGNlbGwuY291bnQpWzFdIH4gdGltZSwgZGF0YT1oMTA0OF9zdW1jLCB0eXBlPSJsIiwgeWxhYj0iUG9wdWxhdGlvbiBkb3VibGluZ3MiKQpgYGAKCgpgYGB7ciBETVM1MywgZmlnLmhlaWdodD05LCBmaWcud2lkdGg9Nn0KcGFyKG1mcm93PWMoMywyKSkKCmludmlzaWJsZShsYXBwbHkobGNjX2NlbGxfbGluZXNbbGNjX2NlbGxfbGluZXMhPSJXTTg4Il0sIGZ1bmN0aW9uKGNsKQp7CiAgICBkdHAgPC0gY3RybHNbW2NsXV0KICAgIGludmlzaWJsZShkby5jYWxsKHBsb3RHQywgYXBwZW5kKGdldEdDYXJncyhkdHAsIGRhdC5jb2w9YygidGltZSIsIkNlbGxfQ291bnQiLCJ1aWQiKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsaXN0KG1haW49cGFzdGUwKGNsLCIsIGNlbGwgY291bnQiKSwgbGVnPUZBTFNFKSkpKQogICAgaW52aXNpYmxlKGRvLmNhbGwocGxvdEdDLCBhcHBlbmQoZ2V0R0NhcmdzKGR0cCwgZGF0LmNvbD1jKCJ0aW1lIiwiUkxVIiwidWlkIikpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGlzdChtYWluPXBhc3RlMChjbCwiLCBsdW0iKSwgbGVnPUZBTFNFLCB5bGltPWMoLTEsMykpKSkpCiAgICBsaW5lcyhzdW1jW3N1bWMkY2VsbC5saW5lPT1jbCwidGltZSJdLCBzdW1jW3N1bWMkY2VsbC5saW5lPT1jbCwicG9wZHVicyJdLCBsd2Q9MykKfSkpCgpgYGAKCgoKYGBge3IgTHVtLW9ubHl9Cmx1bW8gPC0gcmVhZC5jc3YoIi4uL2RhdGEvZnJvbV9DV192aWFfU2xhY2tfMjAyMTA1MDcvMDUwODE5X1JUZ2xvd0RGLmNzdiIpCmx1bW8gPC0gbHVtb1ssLTFdCgpsdW1vX2NlbGxfbGluZXMgPC0gdW5pcXVlKGx1bW8kY2VsbC5saW5lKQpsdW1vX2N0cmxfZGF0IDwtIGxhcHBseShsdW1vX2NlbGxfbGluZXMsIGZ1bmN0aW9uKGNsKSBsdW1vW2x1bW8kY2VsbC5saW5lPT1jbCAmIGx1bW8kZHJ1ZzEuY29uYz09MCAmIGx1bW8kZHJ1ZzEhPSJETVNPIixdKQpuYW1lcyhsdW1vX2N0cmxfZGF0KSA8LSBsdW1vX2NlbGxfbGluZXMKYGBgCgpgYGB7ciBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD04fQpwYXIobWZyb3c9YygyLDQpKQoKaW52aXNpYmxlKGxhcHBseShsdW1vX2NlbGxfbGluZXMsIGZ1bmN0aW9uKGNsKQp7CiAgICBkdHAgPC0gbHVtb19jdHJsX2RhdFtbY2xdXQogICAgIyBwbG90KGR0cCRUb3RIb3VyLCBkdHAkUkxVKQogICAgCiAgICBpbnZpc2libGUoZG8uY2FsbChwbG90R0MsIGFwcGVuZChnZXRHQ2FyZ3MoZHRwLCBkYXQuY29sPWMoIlRvdEhvdXIiLCJSTFUiLCJ3ZWxsIiksIGFyZy5uYW1lID0gYygidGltZSIsICJjZWxsLmNvdW50IiwgImlkcyIpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpc3QobWFpbj1jbCwgbGVnPUZBTFNFKSkpKQp9KSkKYGBgCgoKYGBge3J9CmNvcmwyNzkgPC0gbHVtb19jdHJsX2RhdFtbJ0NPUkwyNzknXV0KCmEgPC0gc3Vic2V0KGNvcmwyNzksIGdyZXBsKCIxMiIsIGNvcmwyNzkkd2VsbCkgKQoKZG8uY2FsbChwbG90R0MsIGFwcGVuZChnZXRHQ2FyZ3MoYSwgZGF0LmNvbD1jKCJUb3RIb3VyIiwiUkxVIiwid2VsbCIpLCBhcmcubmFtZSA9IGMoInRpbWUiLCAiY2VsbC5jb3VudCIsICJpZHMiKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsaXN0KG1haW49IkNPUkwyNzkiLCBsZWc9RkFMU0UpKSkKYGBgCgpgYGB7cn0KZG1zNTMgPC0gY3RybHNbWyJETVM1MyJdXQpgYGAKCg==